﻿using System;
using System.Collections.Generic;
using System.Configuration;
using System.Linq;
using System.Reflection;

namespace Providers
{
    /// <summary>
    /// Create instances of providers
    /// </summary>
    public class ProviderFactory
    {
        /// <summary>
        /// Load server providers from the plug ins folder
        /// </summary>
        /// <param name="serverID">The id of the server provider to load</param>
        /// <returns>A provider that impliments IServer</returns>
        public static SQPlugin.ISQPlugin CreateServerProvider(Guid serverID)
        {
            SQPlugin.ISQPlugin server = null;

            // Load plugins if needed
            if (LoadPlugins())
            {
                // Get the required server from the list of plug ins
                server = m_ServerProviders.Where(x => x.PlugInID == serverID).FirstOrDefault();
            }

            return server;
        }


        /// <summary>
        /// Return a list of plug ins and their associated IDs as a dictionary
        /// </summary>
        /// <returns>A dictionary containing the names of each loaded plugin keyed by its id</returns>
        public static Dictionary<Guid, string> GetPlugInList()
        {
            Dictionary<Guid, string> plugInList = new Dictionary<Guid, string>();

            if (LoadPlugins())
            {
                foreach (SQPlugin.ISQPlugin plugin in m_ServerProviders)
                {
                    if (!plugInList.Keys.Contains(plugin.PlugInID))
                    {
                        plugInList.Add(plugin.PlugInID, plugin.PluginName);
                    }
                }
            }

            return plugInList;
        }

        /// <summary>
        /// Create a "SavedDatabases" provider
        /// </summary>
        /// <returns>A provider that impliemnts ISavedDatabases</returns>
        public static ISavedDatabasesProvider CreateSavedDatabasesProvider()
        {
            ISavedDatabasesProvider provider =
                (ISavedDatabasesProvider)LoadProviderAssembly(SaveDatabasesProviderAssembly, typeof(ISavedDatabasesProvider));

            return provider;
        }

        /// <summary>
        /// Create a "Query" provider
        /// </summary>
        /// <returns>A provider that impliments IQueryProvider</returns>
        public static  IQueryProvider CreateQueryProvider()
        {
            IQueryProvider provider = (IQueryProvider) LoadProviderAssembly(QueryProviderAssembly, typeof (IQueryProvider));

            return provider;
        }

        /// <summary>
        /// Create a "UserConfiguration" provider
        /// </summary>
        /// <returns>A provider that impliments IUserConfigurationProvider</returns>
        public static IUserConfigurationProvider CreateUserConfigurationProvider()
        {
            IUserConfigurationProvider provider = (IUserConfigurationProvider)LoadProviderAssembly(UserConfigurationProviderAssembly, typeof(IUserConfigurationProvider));

            return provider;
        }

        /// <summary>
        /// Return the assembly to use for the UserConfiguration provider
        /// </summary>
        private static string UserConfigurationProviderAssembly
        {
            get
            {
                if (string.IsNullOrEmpty(m_UserConfigurationProviderAssembly))
                {
                    // We have not already cached the assembly name to use for the SavedDatabases provider so do so now
                    m_UserConfigurationProviderAssembly = ConfigurationManager.AppSettings["UserConfigurationProvider"];
                }

                return m_UserConfigurationProviderAssembly;
            }
        }

        /// <summary>
        /// Return the assembly to use for the SavedDatabases provider
        /// </summary>
        private static string SaveDatabasesProviderAssembly
        {
            get
            {
                if (string.IsNullOrEmpty(m_SavedDatabasesProviderAssembly))
                {
                    // We have not already cached the assembly name to use for the SavedDatabases provider so do so now
                    m_SavedDatabasesProviderAssembly = ConfigurationManager.AppSettings["SavedDatabasesProvider"];
                }

                return m_SavedDatabasesProviderAssembly;
            }
        }

        /// <summary>
        /// Return the assembly to use for the Query provider
        /// </summary>
        private static string QueryProviderAssembly
        {
            get
            {
                if (string.IsNullOrEmpty(m_QueryProviderAssembly))
                {
                    // We have not already cached the assembly name for the Query provider so we can do so now
                    m_QueryProviderAssembly = ConfigurationManager.AppSettings["QueryProvider"];
                }

                return m_QueryProviderAssembly;
            }
        }

        /// <summary>
        /// Return the location of the plug ins to load
        /// </summary>
        public static string PluginPath
        {
            get
            {
                if (string.IsNullOrEmpty(m_PluginPath))
                {
                    // We have not already cached the location of the plug ins so do so now
                    m_PluginPath = ConfigurationManager.AppSettings["PluginPath"];
                }

                return m_PluginPath;
            }
        }

        /// <summary>
        /// Load an assembly using a specified interface
        /// </summary>
        /// <param name="assemblyPath">The path to the assembly</param>
        /// <param name="providerType">The type of provider interface that the assembly contains</param>
        /// <returns>An object containing an instance of the dynamically loaded provider</returns>
        private static object LoadProviderAssembly(string assemblyPath, Type providerType)
        {
            object result = null;

            try
            {
                Assembly providerAssembly = Assembly.LoadFrom(assemblyPath);

                if (providerAssembly != null)
                {
                    // We have an assembly loaded into memory so get an array of types that support the provided interface
                    // and have a constructor (i.e. we can create instances of them)
                    IEnumerable<Type> types = from t in providerAssembly.GetTypes()
                                              where t.GetInterfaces().Contains(providerType)
                                                    && t.GetConstructor(Type.EmptyTypes) != null
                                              select t;

                    // A provider should only contain 1 class so use the 1st type we found that supported our interface
                    Type typeToUse = types.FirstOrDefault();
                    if (typeToUse != null)
                    {
                        result = providerAssembly.CreateInstance(typeToUse.FullName);
                    }
                }
            }
            catch (Exception)
            {
                // Something has gone wrong so return null
                result = null;
            }

            return result;
        }

        /// <summary>
        /// Load all available plug ins if needed
        /// </summary>
        /// <returns>True if any plugins were loaded</returns>
        private static bool LoadPlugins()
        {
            bool result;

            if (m_ServerProviders == null)
            {
                PluginLoader pluginLoader = new PluginLoader();
                m_ServerProviders = pluginLoader.LoadServerPlugins(PluginPath);
            }

            // Set result to true if we have plug ins, otherwise set it to fale
            result = (m_ServerProviders == null) ? false : true;

            return result;
        }

        /// <summary>
        /// A list of server provider plug ins that are available for use
        /// </summary>
        private static IEnumerable<SQPlugin.ISQPlugin> m_ServerProviders;

        /// <summary>
        /// The assembly to use for the SavedDatabases provider
        /// </summary>
        private static string m_SavedDatabasesProviderAssembly;

        /// <summary>
        /// The assembly to use for the UserConfiguration provider
        /// </summary>
        private static string m_UserConfigurationProviderAssembly;

        /// <summary>
        /// The assembly to use for the Query provider
        /// </summary>
        private static string m_QueryProviderAssembly;

        /// <summary>
        /// The location to load plugins from
        /// </summary>
        private static string m_PluginPath;
    }
}
